import { isInterval, isOfficialShard, log } from '@/ayaka'
import { AnyTransportTask, TransportMemory, TransportTask } from './types'
import { BodyPartArrayType, getCreepWorking } from '@/utils/common'
import { goTo } from '@/modules/move'
import { CONTROLLER_ENERGY } from '@/utils/constants'

/**
 * 填充任务 id
 */
const FILL_TASK_ID = 'FillTask'
/**
 * 自适应搬运标识，当检测到搬运的量大于这个值时，意味着自己能搬多少搬多少，只搬一次
 */
const ADAPTIVE_AMOUNT = 1e7
/**
 * 填充任务目标 id 缓存
 */
const targetIdSet = new Set<string>()

/**
 * transporter 工作函数
 */
export const transporterWork = function (room: MyRoom, transporters: Creep[], transportMemory: TransportMemory) {
    const creeps = transporters.filter((creep) => !creep.spawning)
    if (!creeps.length) return

    targetIdSet.clear()

    assignTask(room, creeps, transportMemory)

    creeps.forEach((creep) => {
        if (!creep.memory.taskId) return
        if (creep.memory.slack) return

        if (creep.memory.taskId === FILL_TASK_ID) {
            executeFillTask(creep, room)
            return
        }

        const task = transportMemory.tasks.find((task) => task.id === creep.memory.taskId)
        if (!task) {
            delete creep.memory.taskId
            return
        }

        switch (task.type) {
            case 'oneToOne':
                executeOneToOneTask(creep, task, transportMemory.tasks, room)
                break
            case 'oneToMore':
                executeOneToMoreTask(creep, task, transportMemory.tasks, room)
                break
            case 'moreToOne':
                executeMoreToOneTask(creep, task, transportMemory.tasks, room)
                break
            default: {
                // 未知任务类型
                const otherTask: never = task
            }
        }
    })
}

/**
 * 任务分配
 */
function assignTask(room: Room, creeps: Creep[], transportMemory: TransportMemory) {
    // 任务分配，爬与任务是一对一的关系
    const taskIds = new Set<string>()
    // 是否已经有爬接取填充任务了
    let hasFillTask = false

    creeps.forEach((creep) => {
        const { taskId, targetId } = creep.memory

        if (!taskId) {
            delete creep.memory.working
            return
        }

        taskIds.add(taskId)

        if (taskId !== FILL_TASK_ID) return

        hasFillTask = true
        // 填充任务，除 spawn 之外的建筑不要同时被多个爬选为目标
        if (targetId && !room.memory.structures.spawn?.[targetId]) {
            targetIdSet.add(targetId)
        }
    })

    const tasks = transportMemory.tasks.filter(
        (task) => !taskIds.has(task.id) && (!room.memory.state.safe?.state || !task.pauseWhenEnemy),
    )
    let taskIndex = 0
    const isOfficial = !isOfficialShard()
    // 是否需要填充任务
    let needFillTask =
        isNeedFillTask(room) &&
        (!hasFillTask || room.energyAvailable < room.energyCapacityAvailable / (isOfficial ? 3 : 2))

    for (const creep of creeps) {
        if (creep.memory.taskId) continue
        if (creep.ticksToLive! < 50 && creep.store.getUsedCapacity() === 0) {
            creep.suicide()
            continue
        }

        const task = tasks[taskIndex]
        if (task) {
            if (task.priority > 5 && needFillTask) {
                giveTask(creep, FILL_TASK_ID)
                needFillTask = false
                continue
            }

            giveTask(creep, task.id)
            taskIndex++
        } else if (needFillTask) {
            giveTask(creep, FILL_TASK_ID)
            needFillTask = false
        }
    }
}

/**
 * 赋予任务并进行一些准备工作
 */
function giveTask(creep: Creep, taskId: string) {
    creep.memory.taskId = taskId

    delete creep.memory.ready
    delete creep.memory.working
    delete creep.memory.targetId
    delete creep.memory.sourceId
    delete creep.memory.data
}

/**
 * 是否需要填充任务（扩展或者塔）
 */
function isNeedFillTask(room: Room) {
    if (room.energyAvailable < room.energyCapacityAvailable) return true

    if (!isInterval(31)) return false

    return !!room.tower.find((t) => t.store['energy'] < 600)
}

/**
 * 判断一个建筑是否需要填充能量
 */
function isStructureNeedFill(structure: AnyStoreStructure) {
    if (structure instanceof StructureTower) {
        return structure.store['energy'] < 850
    }
    return structure.store.getFreeCapacity('energy') > 0
}

/**
 * 寻找最近的填充目标
 */
function findClosestFillTarget(creep: Creep, room: MyRoom) {
    const targets = [...room.extension, ...room.spawn, ...room.tower].filter(
        (s) => isStructureNeedFill(s) && !targetIdSet.has(s.id),
    )
    if (targets.length) {
        return creep.pos.findClosestByRange(targets)
    }
    return null
}

/**
 * 填充任务结束处理
 */
function fillTaskEnd(creep: Creep) {
    delete creep.memory.taskId
}

/**
 * 填充任务能量来源
 */
function useSource(
    room: MyRoom,
    creep: Creep,
    types: (STRUCTURE_STORAGE | STRUCTURE_TERMINAL | STRUCTURE_FACTORY | STRUCTURE_CONTAINER)[],
) {
    for (const type of types) {
        if (type === STRUCTURE_STORAGE) {
            if (room.storage && room.storage.store['energy'] > 1000) {
                creep.memory.sourceId = room.storage.id
                return
            }
        }
        if (type === STRUCTURE_TERMINAL) {
            if (room.terminal && room.terminal.store['energy'] > 1000) {
                creep.memory.sourceId = room.terminal.id
                return
            }
        }
        if (type === STRUCTURE_FACTORY) {
            if (room.controller.level >= 7 && room.factory && room.factory.store['energy'] > 1000) {
                creep.memory.sourceId = room.factory.id
                return
            }
        }
        if (type === STRUCTURE_CONTAINER) {
            // 优先使用离中心近的 container
            const centerPos = room.memory.center
                ? new RoomPosition(room.memory.center[0], room.memory.center[1], room.name)
                : room.controller.pos
            const containers = room.container
                .filter((c) => c.store['energy'] > 400)
                .sort((a, b) => a.pos.getRangeTo(centerPos) - b.pos.getRangeTo(centerPos))
            if (containers.length) {
                creep.memory.sourceId = containers[0].id
                return
            }
        }
    }
}

/**
 * 执行填充任务
 */
function executeFillTask(creep: Creep, room: MyRoom) {
    if (!creep.memory.working && creep.store['energy'] > 0) {
        creep.memory.working = true
    }
    if (creep.memory.working && creep.store['energy'] === 0) {
        fillTaskEnd(creep)
    }

    if (creep.memory.working) {
        if (!creep.memory.targetId) {
            const target = findClosestFillTarget(creep, room)
            if (target) {
                creep.memory.targetId = target.id
                targetIdSet.add(target.id)
            }
        }

        if (!creep.memory.targetId) {
            fillTaskEnd(creep)
            return
        }

        const target = Game.getObjectById(creep.memory.targetId as Id<AnyStoreStructure>)

        if (!target || !isStructureNeedFill(target)) {
            delete creep.memory.targetId
            return
        }

        if (!creep.pos.isNearTo(target)) {
            goTo(creep, target.pos)
        } else {
            const result = creep.transfer(target, 'energy')
            if (result === OK && creep.store['energy'] > target.store.getFreeCapacity('energy')) {
                // “吃着锅里的，看着碗里的”，填当前的同时寻找下一个目标
                const target = findClosestFillTarget(creep, room)
                if (target) {
                    creep.memory.targetId = target.id
                    targetIdSet.add(target.id)
                    if (!creep.pos.isNearTo(target)) {
                        goTo(creep, target.pos)
                    }
                }
            }
        }
    } else {
        if (!creep.memory.sourceId) {
            useSource(room, creep, ['storage', 'terminal', 'factory', 'container'])
        }

        if (!creep.memory.sourceId) {
            fillTaskEnd(creep)
            return
        }

        const source = Game.getObjectById(creep.memory.sourceId as Id<AnyStoreStructure>)

        if (!source || source.store['energy'] < Math.min(creep.store.getFreeCapacity(), 400)) {
            delete creep.memory.sourceId
            return
        }

        if (creep.withdraw(source, 'energy') === ERR_NOT_IN_RANGE) {
            goTo(creep, source.pos)
        }
    }
}

/**
 * 删除运输任务
 */
export function deleteTransportTask(tasks: AnyTransportTask[], id: string) {
    const index = tasks.findIndex((task) => task.id === id)
    if (~index) {
        tasks.splice(index, 1)
    }
}

function deleteTask(creep: Creep, tasks: AnyTransportTask[], id: string) {
    delete creep.memory.taskId
    deleteTransportTask(tasks, id)
}

type ResInfo = TransportTask<'oneToOne'>['data']['resInfo']

/**
 * 找到所有需要搬运的资源
 */
function findAllRequiredResource(resInfo: ResInfo) {
    const res: ResourceConstant[] = []
    for (const resType in resInfo) {
        const type = resType as ResourceConstant
        if (resInfo[type]!.require > resInfo[type]!.has) {
            res.push(type)
        }
    }
    return res
}

/**
 * 找到第一种需要搬运的资源
 */
function findFirstRequiredResource(resInfo: ResInfo) {
    for (const resType in resInfo) {
        const type = resType as ResourceConstant
        if (resInfo[type]!.require > resInfo[type]!.has) {
            return type
        }
    }
    return undefined
}

/**
 * 检测是否有与任务身上相冲的资源，有则先处理掉
 */
function checkCreepReady(creep: Creep, room: Room, resInfo?: ResInfo) {
    if (creep.memory.ready) {
        return true
    }

    if (creep.store.getUsedCapacity() === 0) {
        return (creep.memory.ready = true)
    }

    if (resInfo && creep.memory.ready === undefined) {
        const res = findAllRequiredResource(resInfo)
        if (
            res.length === 0 ||
            (creep.store['energy'] === creep.store.getUsedCapacity() &&
                res.length === 1 &&
                res[0] === 'energy' &&
                creep.store.getUsedCapacity() < creep.store.getCapacity() / 5)
        ) {
            return (creep.memory.ready = true)
        }
        creep.memory.ready = false
    }

    // 找到有空闲的建筑
    let struct: StructureStorage | StructureTerminal | null = null
    // 优先选择 terminal，因为 storage 可能由于 pc 扩容后恢复容量导致突然溢出
    if (room.terminal && room.terminal.store.getFreeCapacity() > 10000) struct = room.terminal
    else if (room.storage && room.storage.store.getFreeCapacity() > 10000) struct = room.storage

    if (struct) {
        if (!creep.pos.isNearTo(struct)) {
            goTo(creep, struct.pos)
        } else {
            creep.transfer(struct, Object.keys(creep.store)[0] as ResourceConstant)
        }
    } else {
        creep.drop(Object.keys(creep.store)[0] as ResourceConstant)
    }

    return false
}

/**
 * 执行一对一运输任务
 */
function executeOneToOneTask(creep: Creep, task: TransportTask<'oneToOne'>, tasks: AnyTransportTask[], room: MyRoom) {
    getCreepWorking(creep)

    const { data } = task

    // 检测是否有与任务身上相冲突的资源，有则先处理掉
    if (!checkCreepReady(creep, room, data.resInfo)) {
        return
    }

    if (creep.memory.working) {
        const target = Game.getObjectById(data.targetId)

        if (!target) {
            deleteTask(creep, tasks, task.id)
            log(`无法找到目标 ${data.targetId}，已删除任务`, ['Transport'], 'yellow')
            return
        }

        if (!creep.pos.isNearTo(target)) {
            goTo(creep, target.pos)
            return
        }

        const result = creep.transfer(target, Object.keys(creep.store)[0] as ResourceConstant)
        if (result === OK) {
            return
        }

        if (result === ERR_FULL) {
            log(`${target} 目标无法携带更多资源`, ['Transport'], 'yellow')
        } else {
            log(`运输任务 ${data.sourceId} -> ${target} 执行失败，错误码 ${result}`, ['Transport'], 'red')
        }
        deleteTask(creep, tasks, task.id)
    } else {
        const type = findFirstRequiredResource(data.resInfo)
        if (!type) {
            // 找不到需要搬运的资源，自己身上也没有，说明任务完成
            if (creep.store.getUsedCapacity() === 0) {
                deleteTask(creep, tasks, task.id)
            } else {
                creep.memory.working = true
            }
            return
        }

        const source = Game.getObjectById(data.sourceId)
        if (!source || source.store[type] === 0) {
            deleteTask(creep, tasks, task.id)
            log(`来源 ${data.sourceId} 不存在或者无资源${type}，已删除任务`, ['Transport'], 'red')
            return
        }

        if (!creep.pos.isNearTo(source)) {
            goTo(creep, source.pos)
            return
        }

        const info = data.resInfo[type]!
        const amount = Math.min(info.require - info.has, source.store[type], creep.store.getFreeCapacity(type))
        const result = creep.withdraw(source, type, amount)
        if (result === OK) {
            if (info.require >= ADAPTIVE_AMOUNT) {
                info.has = info.require
            } else {
                info.has += amount
            }
        }
    }
}

/**
 * 执行一对多运输任务，例如：storage -> 多个 lab
 */
function executeOneToMoreTask(creep: Creep, task: TransportTask<'oneToMore'>, tasks: AnyTransportTask[], room: MyRoom) {
    getCreepWorking(creep)

    const { data } = task

    // 检测是否有与任务身上相冲突的资源，有则先处理掉
    if (!checkCreepReady(creep, room)) {
        return
    }

    if (creep.memory.working) {
        const creepData = creep.memory.data as string[]

        if (!creepData.length) {
            creep.memory.working = false
            delete creep.memory.ready
            return
        }

        const firstTarget = Game.getObjectById(creepData[0] as Id<AnyStoreStructure>)
        if (!firstTarget) {
            creepData.shift()
            data.targetInfo = data.targetInfo.filter((info) => info.id !== creepData[0])
            return
        }

        if (!creep.pos.isNearTo(firstTarget)) {
            goTo(creep, firstTarget.pos)
            return
        }

        const targetInfo = data.targetInfo.find((info) => info.id === creepData[0])
        if (!targetInfo) {
            creepData.shift()
            return
        }

        const amount = Math.min(creep.store[targetInfo.type], targetInfo.require - targetInfo.has)
        const result = creep.transfer(firstTarget, targetInfo.type, amount)
        if (result === OK) {
            targetInfo.has += amount
        }
        // 删除任务，防止阻碍其他任务
        if (targetInfo.has >= targetInfo.require || result != OK) {
            data.targetInfo = data.targetInfo.filter((info) => info.id !== targetInfo.id)
        }
        creepData.shift()
    } else {
        const source = Game.getObjectById(data.sourceId)
        if (!source) {
            deleteTask(creep, tasks, task.id)
            log(`来源 ${data.sourceId} 不存在，已删除任务`, ['Transport'], 'red')
            return
        }

        if (creep.store.getUsedCapacity() === 0 || !creep.memory.data) {
            // 记录搬运中的目标 id
            creep.memory.data = []
        }
        const creepData = creep.memory.data as string[]

        const targetResInfo = data.targetInfo.find((info) => {
            if (creepData.includes(info.id)) return false

            const amount = info.require - info.has
            return amount > 0 && source.store[info.type] >= amount
        })

        if (!targetResInfo) {
            if (creep.store.getUsedCapacity() === 0) {
                deleteTask(creep, tasks, task.id)
            } else {
                creep.memory.working = true
            }
            return
        }

        // 注意到如下事实：搬运工要么一次性能搬运第一个目标需要的所有资源，要么只能搬运第一个目标的部分资源（这时候其他目标肯定不会去搬运）
        // 所以搬运资源到目标时，只需要转移 Math.min(creep.store[type], require-has)即可
        if (!creep.pos.isNearTo(source)) {
            goTo(creep, source.pos)
            return
        }

        const amount = Math.min(creep.store.getFreeCapacity(), targetResInfo.require - targetResInfo.has)
        const result = creep.withdraw(source, targetResInfo.type, amount)
        if (result === OK) {
            creep.memory.data.push(targetResInfo.id)
        }
    }
}

/**
 * 执行多对一运输任务
 */
function executeMoreToOneTask(creep: Creep, task: TransportTask<'moreToOne'>, tasks: AnyTransportTask[], room: MyRoom) {
    getCreepWorking(creep)

    const { data } = task

    // 检测是否有与任务身上相冲突的资源，有则先处理掉
    if (!checkCreepReady(creep, room)) {
        return
    }

    if (creep.memory.working) {
        const target = Game.getObjectById(data.targetId)
        if (!target) {
            deleteTask(creep, tasks, task.id)
            log(`无法找到目标 ${data.targetId}，已删除任务`, ['Transport'], 'red')
            return
        }

        if (!creep.pos.isNearTo(target)) {
            goTo(creep, target.pos)
            return
        }

        const result = creep.transfer(target, Object.keys(creep.store)[0] as ResourceConstant)
        if (result === OK) {
            return
        }

        if (result === ERR_FULL) {
            log(`${target} 目标无法携带更多资源`, ['Transport'], 'yellow')
        } else {
            log(`运输任务 -> ${target} 执行失败，错误码 ${result}`, ['Transport'], 'red')
        }
        deleteTask(creep, tasks, task.id)
    } else {
        const sourceInfo = data.sourceInfo.find((info) => info.require > info.has)
        if (!sourceInfo) {
            if (creep.store.getUsedCapacity() === 0) {
                deleteTask(creep, tasks, task.id)
            } else {
                creep.memory.working = true
            }
            return
        }

        const source = Game.getObjectById(sourceInfo.id)
        if (!source || source.store[sourceInfo.type] < sourceInfo.require - sourceInfo.has) {
            data.sourceInfo = data.sourceInfo.filter((info) => info.id !== sourceInfo.id)
            return
        }

        if (!creep.pos.isNearTo(source)) {
            goTo(creep, source.pos)
            return
        }

        const amount = Math.min(creep.store.getFreeCapacity(), sourceInfo.require - sourceInfo.has)
        const result = creep.withdraw(source, sourceInfo.type, amount)
        if (result === OK) {
            sourceInfo.has += amount
        }
    }
}

/**
 * 生成 transporter 身体部件
 */
export const createTransporterBody = (room: MyRoom): BodyPartArrayType => {
    const energyCapacityAvailable = room.energyCapacityAvailable
    const base = [CARRY, CARRY, MOVE]
    if (energyCapacityAvailable >= CONTROLLER_ENERGY[7]) {
        return [base, 15]
    } else if (energyCapacityAvailable >= CONTROLLER_ENERGY[5]) {
        return [base, 12]
    } else if (energyCapacityAvailable >= CONTROLLER_ENERGY[4]) {
        return [base, 10]
    } else if (energyCapacityAvailable >= CONTROLLER_ENERGY[3]) {
        return [base, 8]
    } else if (energyCapacityAvailable >= CONTROLLER_ENERGY[2]) {
        return [base, 5]
    } else {
        return [base, 3]
    }
}
